STEP 3 - Review results from DTM_inferLDATopics_LabelCorpus

This notebook: 1. Reads in labeled outputs from 04_pq_model.Rmd (“04_pq_labels.csv”) 2. Joins “04_pq_labels.csv” with “01_pq_metaclean.csv” 3. Subsets dataset based on labels and percent probabilities 4. Writes subsets to CSVs

Notes

  1. “SUBSETTING” keyword for searching 05_pq_label_review.Rmd
#load data
# 01_pq_metaclean.csv
pq_metaclean <- data.table::fread('Data/02_Working/01_pq_metaclean.csv')
# read in columns as characters so that doc id does not read in as numeric
# 04_pq_labels.csv
pq_labels <- data.table::fread(paste0('Data/02_Working/',rFileModelNum,'_pq_labels.csv'), colClasses = 'character')

# Head displays the first 6 rows of the data.table
#head(pq_metaclean)
#head(pq_labels)
# displays column names
print("pq_metaclean columns:")
[1] "pq_metaclean columns:"
names(pq_metaclean)
 [1] "Title"                   "Publication title"       "Publication year"        "Document URL"            "Full text"              
 [6] "Links"                   "Section"                 "Publication subject"     "ISSN"                    "Copyright"              
[11] "Abstract"                "Publication info"        "Last updated"            "Place of publication"    "Location"               
[16] "Author"                  "Publisher"               "Identifier / keyword"    "Source type"             "ProQuest document ID"   
[21] "Country of publication"  "Language of publication" "Publication date"        "Subject"                 "Database"               
[26] "Document type"          
print("")
[1] ""
print("pq_labels columns:")
[1] "pq_labels columns:"
names(pq_labels)
[1] "document" "topic"    "val"     
nrow(pq_metaclean);nrow(pq_labels)
[1] 6239
[1] 4881

join cleaned dataset with labels

# join tables
# inner_join because pq_metaclean was subset based on topic 2 when the model was re-ran in "04_pq_model.Rmd"
# so we only want where the Proquest ID exists in both the original dataset and the labels.
pq_metajoin <- pq_labels %>% 
  inner_join(pq_metaclean, by = c("document" = "ProQuest document ID"))

pq_metajoin <- pq_metajoin %>%
  rename(`ProQuest document ID` = document)

nrow(pq_metajoin);head(pq_metajoin)
[1] 4880
pq_empty<-pq_metajoin[pq_metajoin$topic == "" | is.na(pq_metajoin$topic),]
nrow(pq_empty)
[1] 0
# write labeled corpus to CSV
outputFileName = "pq_topics"
outputFile = paste(outputFolder,rFileNum,"_",outputFileName,".xlsx",sep="")
write.xlsx(pq_metajoin, outputFile, row.names=FALSE)
#write.htmltable(pq_metajoin,title=outputFile, outputFile, sortby="topic","val")

Code now ready for SUBSETTING if you want to skip the following sections –

exploratory data analysis of topics generated

Do we want to keep both topics, or drop one of the topics and dig deeper into the other topic?

number of topics by publication title

plotTitle = "Count of Articles by Topic and Publication Title"

# count by pub title and topic
count_topic_pubtitle <-pq_metajoin %>%
  group_by(`Publication title`,topic) %>% 
  summarise(n= n()) %>%
  arrange(as.numeric(topic),as.numeric(n))
`summarise()` regrouping output by 'Publication title' (override with `.groups` argument)
count_topic_pubtitle

# save plot in png format
outputPNGFileName <- file.path(outputPngFolder,paste0("count_topic_pubtitle.png"))
png(outputPNGFileName,height=6,width=12, units='in', res=300)
ggplot(count_topic_pubtitle, aes(x = as.factor(as.numeric(topic)), y = n, fill = `Publication title`, label = n )) +
  geom_bar(stat = "identity") +
  geom_text(size = 3, position = position_stack(vjust = 0.5)) +
  ggtitle(plotTitle) +
  theme(plot.title = element_text(hjust = 0.5)) +
  xlab("Probability Range") +
  ylab("Article Count") +
  labs(fill = "Topic")
print(paste("Plot saved as:",outputPNGFileName))
[1] "Plot saved as: Images//05_pq_review/count_topic_pubtitle.png"
dev.off()
null device 
          1 
knitr::include_graphics(paste0("Images/",rFileNum,"_pq_review/count_topic_pubtitle.png"))

number of topics by location

Count articles by topic and year

Important to consider the total articles/year in addition to the raw count of articles per topic. Peaks in articles may simply be due to an overall increase of articles for a particular year.

# article count per year and topic
count_topic_year <-pq_metajoin %>%
  group_by(`Publication year`,topic) %>% 
  summarise(n= n()) %>%
  arrange(as.numeric(topic),as.numeric(`Publication year`))
`summarise()` regrouping output by 'Publication year' (override with `.groups` argument)
count_topic_year

# article count per year
count_year <-pq_metajoin %>%
  group_by(`Publication year`) %>% 
  summarise(perYear= n()) %>%
  arrange(as.numeric(`Publication year`))
`summarise()` ungrouping output (override with `.groups` argument)
count_topic_year<-count_topic_year %>%
  left_join(count_year, by = "Publication year")

count_topic_year<-count_topic_year %>%
  mutate(ratio = round(n/perYear,2))

plotTitle = "Count of Articles by Topic and Year"
# save plot in png format
outputPNGFileName <- file.path(outputPngFolder,paste0("count_topic_year.png"))
png(outputPNGFileName,height=5,width=15, units='in', res=300)
ggplot(count_topic_year, aes(x = `Publication year`, colour = as.factor(as.numeric(topic)))) +
  geom_line(aes(y = n)) +
  scale_x_continuous(breaks=seq(min(na.omit(count_topic_year$`Publication year`)), max(na.omit(count_topic_year$`Publication year`)),1)) +
  theme(axis.text.x = element_text(angle = 90),
        plot.title = element_text(hjust = 0.5)) +
  facet_wrap(vars(as.numeric(topic))) +
  ggtitle(plotTitle) +
  xlab("Publication Year") +
  ylab("Article Count") +
  guides(colour=FALSE) 
print(paste("Plot saved as:",outputPNGFileName))
[1] "Plot saved as: Images//05_pq_review/count_topic_year.png"
dev.off()
null device 
          1 
plotTitle = "Count of Articles by Topic and Year with Year Total"
# save plot in png format
outputPNGFileName <- file.path(outputPngFolder,paste0("count_topic_year_tot.png"))
png(outputPNGFileName,height=5,width=15, units='in', res=300)
ggplot(count_topic_year, aes(x = `Publication year`, colour = as.factor(as.numeric(topic)))) +
  geom_line(aes(y = n)) +
  geom_line(linetype = "dashed", color="black", aes(y = perYear)) +
  scale_x_continuous(breaks=seq(min(na.omit(count_topic_year$`Publication year`)), max(na.omit(count_topic_year$`Publication year`)),1)) +
  theme(axis.text.x = element_text(angle = 90),
        plot.title = element_text(hjust = 0.5)) +
  facet_wrap(vars(as.numeric(topic))) +
  ggtitle(plotTitle) +
  xlab("Publication Year") +
  ylab("Article Count") +
  guides(colour=FALSE) 
print(paste("Plot saved as:",outputPNGFileName))
[1] "Plot saved as: Images//05_pq_review/count_topic_year_tot.png"
dev.off()
null device 
          1 
plotTitle = "Ratio of Articles by Topic and Year"
# save plot in png format
outputPNGFileName <- file.path(outputPngFolder,paste0("ratio_topic_year.png"))
png(outputPNGFileName,height=5,width=15, units='in', res=300)
ggplot(count_topic_year, aes(x = `Publication year`, colour = as.factor(as.numeric(topic)))) +
  geom_line(aes(y = ratio)) +
  scale_x_continuous(breaks=seq(min(na.omit(count_topic_year$`Publication year`)), max(na.omit(count_topic_year$`Publication year`)),1)) +
  theme(axis.text.x = element_text(angle = 90),
        plot.title = element_text(hjust = 0.5)) +
  facet_wrap(vars(as.numeric(topic))) +
  ggtitle(plotTitle) +
  xlab("Publication Year") +
  ylab("(Article Count)/(Article Total)") +
  guides(colour=FALSE) 
print(paste("Plot saved as:",outputPNGFileName))
[1] "Plot saved as: Images//05_pq_review/ratio_topic_year.png"
dev.off()
null device 
          1 
knitr::include_graphics(paste0("Images/",rFileNum,"_pq_review/count_topic_year.png"))

knitr::include_graphics(paste0("Images/",rFileNum,"_pq_review/count_topic_year_tot.png"))

knitr::include_graphics(paste0("Images/",rFileNum,"_pq_review/ratio_topic_year.png"))

Coherence score by topic

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/coherence_score_topic.png"))

Wordclouds

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/1_lda_topic_wc.png"))

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/2_lda_topic_wc.png"))

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/3_lda_topic_wc.png"))

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/4_lda_topic_wc.png"))

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/5_lda_topic_wc.png"))

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/6_lda_topic_wc.png"))

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/7_lda_topic_wc.png"))

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/8_lda_topic_wc.png"))

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/9_lda_topic_wc.png"))

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/10_lda_topic_wc.png"))

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/11_lda_topic_wc.png"))

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/12_lda_topic_wc.png"))

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/13_lda_topic_wc.png"))

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/14_lda_topic_wc.png"))

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/15_lda_topic_wc.png"))

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/16_lda_topic_wc.png"))

Count of Article by Topic

# bar chart
knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/topic_count_bar.png"))


# pie chart
knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/topic_count_pie.png"))

Count of Probabilities by Percentage

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/prob_count.png"))

Count of Topic Probabilities by Percentage

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/prob_topic_count.png"))

#5. Visualising of topics in a dendrogram

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/hclust_dendrogram.png"))

NA

“Quarantine”

# not_oyster_subset = if not exists in full_corpus {oyster*}
# 1858 articles with the word "oyster"
# 3286 articles without the word "oyster"
pq_metaqtine<- pq_metajoin %>% 
  filter(!stringr::str_detect(`Full text`,"oyster*")) 
print(paste("Number of articles without the word oyster*", nrow(pq_metaqtine)))
[1] "Number of articles without the word oyster* 3120"
head(pq_metaqtine)

# quarantined_subset = if exists in not_oyster_subset {clam* | mussel* | scallop* | lobster*}
#These are the quarantined articles that need to be examined
pq_metaqtine <- pq_metaqtine %>% 
  filter(stringr::str_detect(`Full text`,"clam* | mussel* | scallop* | lobster*"))
print(paste("Number of articles within *not oyster* subset and has the words clam* | mussel* | scallop* | lobster*", nrow(pq_metaqtine)))
[1] "Number of articles within *not oyster* subset and has the words clam* | mussel* | scallop* | lobster* 1422"
pq_metaqtine_ids <- pq_metaqtine %>% select(`ProQuest document ID`)
# corpus_subset = anti_join(quarantined_subset, fullcorpus)

# this is the entire corpus with quarantined articles removed
pq_metafilter_words <- pq_metajoin %>%
  anti_join(pq_metaqtine_ids, by="ProQuest document ID")

numFiltered <- nrow(pq_metajoin)-nrow(pq_metafilter_words)
rm(pq_metaqtine_ids)

print(paste("Number of articles removed based on words:",numFiltered));nrow(pq_metafilter_words)
[1] "Number of articles removed based on words: 1422"
[1] 3459

START SUBSETTING - Subset Function

topic_subset_csv <- function(outputFolderName, rFileNum, outputFileName, inputCorpus, minPerc, maxPerc, theTopic, overwrite)

  • outputFolderName = folder to save to – i.e., “Data/02_Working/” (this is set at the top of the code where the libraries are loaded/installed)
  • rFileNum = The number in the r file – i.e., “05” (this is set at the top of the code where the libraries are loaded/installed)
  • outputFileName = the file name to save as – i.e., “pq_topic5_90perc” (this needs to be set at least 1 line above where the function is ran)
  • inputCorpus = the labeled corpus – i.e., pq_metajoin (this is created shortly after where libraries are loaded/installed in review.Rmd by joining pq_metaclean with pq_labels)
  • minPerc = minimum percentage – i.e., 0.9 (this needs to be set at least 1 line above where the function is ran)
  • maxPerc = maximum percentage – i.e., 1 (this needs to be set at least 1 line above where the function is ran)
  • theTopic = the topic category – i.e., “1” (this needs to be set at least 1 line above where the function is ran)
  • overwrite = whether to overwrite the file if it already exists – i.e., FALSE (this needs to be set at least 1 line above where the function is ran)

Subset topic 1

# subset corpus to unique identifier & full text of article
# investigate topic 1

outputFileName_1t <- "pq_topic1"
minPerc <- 0
maxPerc <- 1
theTopic <- "1"

## e.g, minPerc = 0 & maxPerc = 0.4 is zero to 0.3999999999 ...
pq_topic1_subset<-topic_subset_csv(outputFolder, rFileNum, outputFileName_1t, pq_metajoin , minPerc, maxPerc, theTopic, overwrite)

Already exists, loading: pq_topic1 
head(pq_topic1_subset);nrow(pq_topic1_subset)
[1] 233

Subset topic 1, 90%

# subset corpus to unique identifier & full text of article
# investigate topic 1, 90%
outputFile_1t90perc <- "pq_topic1_90perc"
minPerc <- 0.9
maxPerc <- 1
theTopic <- "1"

## e.g, minPerc = 0 & maxPerc = 0.4 is zero to 0.3999999999 ...
pq_topic1_90perc<-topic_subset_csv(outputFolder, rFileNum, outputFile_1t90perc, pq_metajoin , minPerc, maxPerc, theTopic, overwrite)

Already exists, loading: pq_topic1_90perc 
head(pq_topic1_90perc);nrow(pq_topic1_90perc)
[1] 55

Subset topic 5, 90%

# subset corpus to unique identifier & full text of article
# investigate topic 1, 90%
outputFile_5t90perc <- "pq_topic5_90perc"
minPerc <- 0.9
maxPerc <- 1
theTopic <- "5"

## e.g, minPerc = 0 & maxPerc = 0.4 is zero to 0.3999999999 ...
pq_topic5_90perc<-topic_subset_csv(outputFolder, rFileNum, outputFile_5t90perc, pq_metajoin , minPerc, maxPerc, theTopic, overwrite)

Already exists, loading: pq_topic5_90perc 
head(pq_topic5_90perc);nrow(pq_topic5_90perc)
[1] 80

Subset 50-60%-ers

# subset corpus to unique identifier & full text of article
# investigate all topics, 50-60%
outputFile_56perc <- "pq_56perc"
minPerc <- 0.5
maxPerc <- 0.6
theTopic <- "all"

## e.g, minPerc = 0 & maxPerc = 0.4 is zero to 0.3999999999 ...
pq_56perc<-topic_subset_csv(outputFolder, rFileNum, outputFile_56perc, pq_metajoin , minPerc, maxPerc, theTopic, overwrite)

Already exists, loading: pq_56perc 
head(pq_56perc);nrow(pq_56perc)
[1] 723

Subset topic 1

max(pq_perc06_subset$val)
[1] "0.59996"

END SUBSETTING

explore term frequencies by year - how do words in our corpus change over time?

Referenced walk-through from: https://cran.r-project.org/web/packages/tidytext/vignettes/tidying_casting.html

# subset corpus to unique identifier & year
pq_time <- pq_metaclean %>% 
  select(`ProQuest document ID`, `Publication year`)

# tokens generated from 04_pq_model.Rmd
outputTokenFile = paste0(rFileModelNum,"_tokens.RData")
tokensFileName = file.path(outputFolder,outputTokenFile)

load(file=tokensFileName)

tokens <- tokens %>% 
  full_join(pq_time, by = c("ProQuest document ID" = "ProQuest document ID")) %>%
  rename(Year = `Publication year`) %>%
  rename(pq_id = `ProQuest document ID`) %>%
  mutate_at(vars(Year), funs(as.integer))
`funs()` is deprecated as of dplyr 0.8.0.
Please use a list of either functions or lambdas: 

  # Simple named list: 
  list(mean = mean, median = median)

  # Auto named with `tibble::lst()`: 
  tibble::lst(mean, median)

  # Using lambdas
  list(~ mean(., trim = .2), ~ median(., na.rm = TRUE))
This warning is displayed once every 8 hours.
Call `lifecycle::last_warnings()` to see where this warning was generated.
rm(pq_time)

outputFreqsFile = paste0(rFileNum,"_tokens_freq")
tokens_freq<-create_ifnot_tokens_freq(outputFolder, outputFreqsFile, tokens, overwrite)

Already exists, loading: 05_tokens_freq 
outputFreqModelFile = paste0(rFileNum,"_freq_models")
freq_models <- create_ifnot_freqmodels(outputFolder, outputFreqModelFile, tokens_freq, overwrite)

Already exists, loading: 05_freq_models 

model results

freq_models %>%
  filter(term == "Year") %>%
  arrange(desc(abs(estimate)))

Models displayed as a volcano plot, which compares the effect size with the significance

freq_models %>%
  mutate(adjusted.p.value = p.adjust(p.value)) %>%
  ggplot(aes(estimate, adjusted.p.value)) +
  geom_point() +
  scale_y_log10() +
  geom_text(aes(label = word), vjust = 1, hjust = 1,
            check_overlap = TRUE) +
  xlab("Estimated change over time") +
  ylab("Adjusted p-value")

Top 6 terms that have changed in frequency over time

freq_models %>%
  top_n(6, abs(estimate)) %>%
  inner_join(tokens_freq) %>%
  ggplot(aes(Year, percent)) +
  geom_point() +
  geom_smooth() +
  facet_wrap(~ word) +
  scale_y_continuous(labels = percent_format()) +
  ylab("Frequency of word in speech")
Joining, by = "word"

LS0tDQp0aXRsZTogInBxX2xhYmVsX3JldmlldyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiMgU1RFUCAzIC0gUmV2aWV3IHJlc3VsdHMgZnJvbSBEVE1faW5mZXJMREFUb3BpY3NfTGFiZWxDb3JwdXMNCg0KVGhpcyBub3RlYm9vazoNCjEuIFJlYWRzIGluIGxhYmVsZWQgb3V0cHV0cyBmcm9tIDA0X3BxX21vZGVsLlJtZCAoIjA0X3BxX2xhYmVscy5jc3YiKQ0KMi4gSm9pbnMgIjA0X3BxX2xhYmVscy5jc3YiIHdpdGggIjAxX3BxX21ldGFjbGVhbi5jc3YiDQozLiBTdWJzZXRzIGRhdGFzZXQgYmFzZWQgb24gbGFiZWxzIGFuZCBwZXJjZW50IHByb2JhYmlsaXRpZXMNCjQuIFdyaXRlcyBzdWJzZXRzIHRvIENTVnMNCg0KIyBOb3Rlcw0KMS4gIlNVQlNFVFRJTkciIGtleXdvcmQgZm9yIHNlYXJjaGluZyAwNV9wcV9sYWJlbF9yZXZpZXcuUm1kDQoNCmBgYHtyLCBlY2hvPUZBTFNFLCByZXN1bHRzPSJoaWRlIiwgbWVzc2FnZXM9RkFMU0V9DQojIExvYWQgYW5kIEluc3RhbGwgTGlicmFyaWVzDQpzb3VyY2UoIlNFTl9mdW5jdGlvbnMuUiIpDQoNCiMjIENoZWNrIGxpYnJhcmllcyAmIGluc3RhbGwNCkxpYnJhcnlMaXN0PC1jKCJzdHJpbmdyIiwiZGF0YS50YWJsZSIsImRwbHlyIiwidGlkeXIiLCJtYWdyaXR0ciIsIk5MUCIsInRpZHl0ZXh0IiwidG0iLCJnZ3Bsb3QyIiwNCiAgICAgICAgICAgICAgICJzY2FsZXMiLCAiZ2d3b3JkY2xvdWQiLCJ0ZXh0bWluZVIiLCJkaWdlc3QiLCAiYnJvb20iLCAic3RyaW5naSIsICJ4bHN4IikNCmluc3RhbGxfb3JfbG9hZF9wYWNrKExpYnJhcnlMaXN0KQ0KDQoNCm91dHB1dEZvbGRlciA9ICJEYXRhLzAyX1dvcmtpbmcvIg0Kb3V0cHV0SW1nRm9sZGVyID0gIkltYWdlcy8iDQpyRmlsZU51bSA9ICIwNSINCnJGaWxlTW9kZWxOdW0gPSAiMDQiDQoNCm91dHB1dFBuZ0ZvbGRlcjwtZmlsZS5wYXRoKG91dHB1dEltZ0ZvbGRlciwgcGFzdGUwKHJGaWxlTnVtLCJfcHFfcmV2aWV3IikpDQppZiAoIWRpci5leGlzdHMob3V0cHV0UG5nRm9sZGVyKSkgZGlyLmNyZWF0ZShvdXRwdXRQbmdGb2xkZXIpDQoNCm92ZXJ3cml0ZSA9IEZBTFNFDQpgYGANCg0KDQpgYGB7cn0NCiNsb2FkIGRhdGENCiMgMDFfcHFfbWV0YWNsZWFuLmNzdg0KcHFfbWV0YWNsZWFuIDwtIGRhdGEudGFibGU6OmZyZWFkKCdEYXRhLzAyX1dvcmtpbmcvMDFfcHFfbWV0YWNsZWFuLmNzdicpDQojIHJlYWQgaW4gY29sdW1ucyBhcyBjaGFyYWN0ZXJzIHNvIHRoYXQgZG9jIGlkIGRvZXMgbm90IHJlYWQgaW4gYXMgbnVtZXJpYw0KIyAwNF9wcV9sYWJlbHMuY3N2DQpwcV9sYWJlbHMgPC0gZGF0YS50YWJsZTo6ZnJlYWQocGFzdGUwKCdEYXRhLzAyX1dvcmtpbmcvJyxyRmlsZU1vZGVsTnVtLCdfcHFfbGFiZWxzLmNzdicpLCBjb2xDbGFzc2VzID0gJ2NoYXJhY3RlcicpDQoNCiMgSGVhZCBkaXNwbGF5cyB0aGUgZmlyc3QgNiByb3dzIG9mIHRoZSBkYXRhLnRhYmxlDQojaGVhZChwcV9tZXRhY2xlYW4pDQojaGVhZChwcV9sYWJlbHMpDQpgYGANCg0KDQpgYGB7cn0NCiMgZGlzcGxheXMgY29sdW1uIG5hbWVzDQpwcmludCgicHFfbWV0YWNsZWFuIGNvbHVtbnM6IikNCm5hbWVzKHBxX21ldGFjbGVhbikNCnByaW50KCIiKQ0KcHJpbnQoInBxX2xhYmVscyBjb2x1bW5zOiIpDQpuYW1lcyhwcV9sYWJlbHMpDQpucm93KHBxX21ldGFjbGVhbik7bnJvdyhwcV9sYWJlbHMpDQpgYGANCg0KIyBqb2luIGNsZWFuZWQgZGF0YXNldCB3aXRoIGxhYmVscw0KYGBge3J9DQojIGpvaW4gdGFibGVzDQojIGlubmVyX2pvaW4gYmVjYXVzZSBwcV9tZXRhY2xlYW4gd2FzIHN1YnNldCBiYXNlZCBvbiB0b3BpYyAyIHdoZW4gdGhlIG1vZGVsIHdhcyByZS1yYW4gaW4gIjA0X3BxX21vZGVsLlJtZCINCiMgc28gd2Ugb25seSB3YW50IHdoZXJlIHRoZSBQcm9xdWVzdCBJRCBleGlzdHMgaW4gYm90aCB0aGUgb3JpZ2luYWwgZGF0YXNldCBhbmQgdGhlIGxhYmVscy4NCnBxX21ldGFqb2luIDwtIHBxX2xhYmVscyAlPiUgDQogIGlubmVyX2pvaW4ocHFfbWV0YWNsZWFuLCBieSA9IGMoImRvY3VtZW50IiA9ICJQcm9RdWVzdCBkb2N1bWVudCBJRCIpKQ0KDQpwcV9tZXRham9pbiA8LSBwcV9tZXRham9pbiAlPiUNCiAgcmVuYW1lKGBQcm9RdWVzdCBkb2N1bWVudCBJRGAgPSBkb2N1bWVudCkNCg0KbnJvdyhwcV9tZXRham9pbik7aGVhZChwcV9tZXRham9pbikNCg0KcHFfZW1wdHk8LXBxX21ldGFqb2luW3BxX21ldGFqb2luJHRvcGljID09ICIiIHwgaXMubmEocHFfbWV0YWpvaW4kdG9waWMpLF0NCm5yb3cocHFfZW1wdHkpDQoNCiMgd3JpdGUgbGFiZWxlZCBjb3JwdXMgdG8gQ1NWDQpvdXRwdXRGaWxlTmFtZSA9ICJwcV90b3BpY3MiDQpvdXRwdXRGaWxlID0gcGFzdGUob3V0cHV0Rm9sZGVyLHJGaWxlTnVtLCJfIixvdXRwdXRGaWxlTmFtZSwiLnhsc3giLHNlcD0iIikNCndyaXRlLnhsc3gocHFfbWV0YWpvaW4sIG91dHB1dEZpbGUsIHJvdy5uYW1lcz1GQUxTRSkNCiN3cml0ZS5odG1sdGFibGUocHFfbWV0YWpvaW4sdGl0bGU9b3V0cHV0RmlsZSwgb3V0cHV0RmlsZSwgc29ydGJ5PSJ0b3BpYyIsInZhbCIpDQpgYGANCiMgQ29kZSBub3cgcmVhZHkgZm9yIFNVQlNFVFRJTkcgaWYgeW91IHdhbnQgdG8gc2tpcCB0aGUgZm9sbG93aW5nIHNlY3Rpb25zIC0tDQoNCg0KIyBleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzIG9mIHRvcGljcyBnZW5lcmF0ZWQNCkRvIHdlIHdhbnQgdG8ga2VlcCBib3RoIHRvcGljcywgb3IgZHJvcCBvbmUgb2YgdGhlIHRvcGljcyBhbmQgZGlnIGRlZXBlciBpbnRvIHRoZSBvdGhlciB0b3BpYz8NCg0KIyBudW1iZXIgb2YgdG9waWNzIGJ5IHB1YmxpY2F0aW9uIHRpdGxlDQpgYGB7ciwgbWVzc2FnZXM9RkFMU0V9DQpwbG90VGl0bGUgPSAiQ291bnQgb2YgQXJ0aWNsZXMgYnkgVG9waWMgYW5kIFB1YmxpY2F0aW9uIFRpdGxlIg0KDQojIGNvdW50IGJ5IHB1YiB0aXRsZSBhbmQgdG9waWMNCmNvdW50X3RvcGljX3B1YnRpdGxlIDwtcHFfbWV0YWpvaW4gJT4lDQogIGdyb3VwX2J5KGBQdWJsaWNhdGlvbiB0aXRsZWAsdG9waWMpICU+JSANCiAgc3VtbWFyaXNlKG49IG4oKSkgJT4lDQogIGFycmFuZ2UoYXMubnVtZXJpYyh0b3BpYyksYXMubnVtZXJpYyhuKSkNCg0KY291bnRfdG9waWNfcHVidGl0bGUNCg0KIyBzYXZlIHBsb3QgaW4gcG5nIGZvcm1hdA0Kb3V0cHV0UE5HRmlsZU5hbWUgPC0gZmlsZS5wYXRoKG91dHB1dFBuZ0ZvbGRlcixwYXN0ZTAoImNvdW50X3RvcGljX3B1YnRpdGxlLnBuZyIpKQ0KcG5nKG91dHB1dFBOR0ZpbGVOYW1lLGhlaWdodD02LHdpZHRoPTEyLCB1bml0cz0naW4nLCByZXM9MzAwKQ0KZ2dwbG90KGNvdW50X3RvcGljX3B1YnRpdGxlLCBhZXMoeCA9IGFzLmZhY3Rvcihhcy5udW1lcmljKHRvcGljKSksIHkgPSBuLCBmaWxsID0gYFB1YmxpY2F0aW9uIHRpdGxlYCwgbGFiZWwgPSBuICkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsNCiAgZ2VvbV90ZXh0KHNpemUgPSAzLCBwb3NpdGlvbiA9IHBvc2l0aW9uX3N0YWNrKHZqdXN0ID0gMC41KSkgKw0KICBnZ3RpdGxlKHBsb3RUaXRsZSkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKw0KICB4bGFiKCJQcm9iYWJpbGl0eSBSYW5nZSIpICsNCiAgeWxhYigiQXJ0aWNsZSBDb3VudCIpICsNCiAgbGFicyhmaWxsID0gIlRvcGljIikNCnByaW50KHBhc3RlKCJQbG90IHNhdmVkIGFzOiIsb3V0cHV0UE5HRmlsZU5hbWUpKQ0KZGV2Lm9mZigpDQoNCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKHBhc3RlMCgiSW1hZ2VzLyIsckZpbGVOdW0sIl9wcV9yZXZpZXcvY291bnRfdG9waWNfcHVidGl0bGUucG5nIikpDQpgYGANCg0KIyBudW1iZXIgb2YgdG9waWNzIGJ5IGxvY2F0aW9uDQoNCiMgQ291bnQgYXJ0aWNsZXMgYnkgdG9waWMgYW5kIHllYXINCkltcG9ydGFudCB0byBjb25zaWRlciB0aGUgdG90YWwgYXJ0aWNsZXMveWVhciBpbiBhZGRpdGlvbiB0byB0aGUgcmF3IGNvdW50IG9mIGFydGljbGVzIHBlciB0b3BpYy4NClBlYWtzIGluIGFydGljbGVzIG1heSBzaW1wbHkgYmUgZHVlIHRvIGFuIG92ZXJhbGwgaW5jcmVhc2Ugb2YgYXJ0aWNsZXMgZm9yIGEgcGFydGljdWxhciB5ZWFyLg0KYGBge3IsIG1lc3NhZ2VzPUZBTFNFfQ0KIyBhcnRpY2xlIGNvdW50IHBlciB5ZWFyIGFuZCB0b3BpYw0KY291bnRfdG9waWNfeWVhciA8LXBxX21ldGFqb2luICU+JQ0KICBncm91cF9ieShgUHVibGljYXRpb24geWVhcmAsdG9waWMpICU+JSANCiAgc3VtbWFyaXNlKG49IG4oKSkgJT4lDQogIGFycmFuZ2UoYXMubnVtZXJpYyh0b3BpYyksYXMubnVtZXJpYyhgUHVibGljYXRpb24geWVhcmApKQ0KDQpjb3VudF90b3BpY195ZWFyDQoNCiMgYXJ0aWNsZSBjb3VudCBwZXIgeWVhcg0KY291bnRfeWVhciA8LXBxX21ldGFqb2luICU+JQ0KICBncm91cF9ieShgUHVibGljYXRpb24geWVhcmApICU+JSANCiAgc3VtbWFyaXNlKHBlclllYXI9IG4oKSkgJT4lDQogIGFycmFuZ2UoYXMubnVtZXJpYyhgUHVibGljYXRpb24geWVhcmApKQ0KDQpjb3VudF90b3BpY195ZWFyPC1jb3VudF90b3BpY195ZWFyICU+JQ0KICBsZWZ0X2pvaW4oY291bnRfeWVhciwgYnkgPSAiUHVibGljYXRpb24geWVhciIpDQoNCmNvdW50X3RvcGljX3llYXI8LWNvdW50X3RvcGljX3llYXIgJT4lDQogIG11dGF0ZShyYXRpbyA9IHJvdW5kKG4vcGVyWWVhciwyKSkNCg0KcGxvdFRpdGxlID0gIkNvdW50IG9mIEFydGljbGVzIGJ5IFRvcGljIGFuZCBZZWFyIg0KIyBzYXZlIHBsb3QgaW4gcG5nIGZvcm1hdA0Kb3V0cHV0UE5HRmlsZU5hbWUgPC0gZmlsZS5wYXRoKG91dHB1dFBuZ0ZvbGRlcixwYXN0ZTAoImNvdW50X3RvcGljX3llYXIucG5nIikpDQpwbmcob3V0cHV0UE5HRmlsZU5hbWUsaGVpZ2h0PTUsd2lkdGg9MTUsIHVuaXRzPSdpbicsIHJlcz0zMDApDQpnZ3Bsb3QoY291bnRfdG9waWNfeWVhciwgYWVzKHggPSBgUHVibGljYXRpb24geWVhcmAsIGNvbG91ciA9IGFzLmZhY3Rvcihhcy5udW1lcmljKHRvcGljKSkpKSArDQogIGdlb21fbGluZShhZXMoeSA9IG4pKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9c2VxKG1pbihuYS5vbWl0KGNvdW50X3RvcGljX3llYXIkYFB1YmxpY2F0aW9uIHllYXJgKSksIG1heChuYS5vbWl0KGNvdW50X3RvcGljX3llYXIkYFB1YmxpY2F0aW9uIHllYXJgKSksMSkpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCksDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArDQogIGZhY2V0X3dyYXAodmFycyhhcy5udW1lcmljKHRvcGljKSkpICsNCiAgZ2d0aXRsZShwbG90VGl0bGUpICsNCiAgeGxhYigiUHVibGljYXRpb24gWWVhciIpICsNCiAgeWxhYigiQXJ0aWNsZSBDb3VudCIpICsNCiAgZ3VpZGVzKGNvbG91cj1GQUxTRSkgDQpwcmludChwYXN0ZSgiUGxvdCBzYXZlZCBhczoiLG91dHB1dFBOR0ZpbGVOYW1lKSkNCmRldi5vZmYoKQ0KDQpwbG90VGl0bGUgPSAiQ291bnQgb2YgQXJ0aWNsZXMgYnkgVG9waWMgYW5kIFllYXIgd2l0aCBZZWFyIFRvdGFsIg0KIyBzYXZlIHBsb3QgaW4gcG5nIGZvcm1hdA0Kb3V0cHV0UE5HRmlsZU5hbWUgPC0gZmlsZS5wYXRoKG91dHB1dFBuZ0ZvbGRlcixwYXN0ZTAoImNvdW50X3RvcGljX3llYXJfdG90LnBuZyIpKQ0KcG5nKG91dHB1dFBOR0ZpbGVOYW1lLGhlaWdodD01LHdpZHRoPTE1LCB1bml0cz0naW4nLCByZXM9MzAwKQ0KZ2dwbG90KGNvdW50X3RvcGljX3llYXIsIGFlcyh4ID0gYFB1YmxpY2F0aW9uIHllYXJgLCBjb2xvdXIgPSBhcy5mYWN0b3IoYXMubnVtZXJpYyh0b3BpYykpKSkgKw0KICBnZW9tX2xpbmUoYWVzKHkgPSBuKSkgKw0KICBnZW9tX2xpbmUobGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3I9ImJsYWNrIiwgYWVzKHkgPSBwZXJZZWFyKSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPXNlcShtaW4obmEub21pdChjb3VudF90b3BpY195ZWFyJGBQdWJsaWNhdGlvbiB5ZWFyYCkpLCBtYXgobmEub21pdChjb3VudF90b3BpY195ZWFyJGBQdWJsaWNhdGlvbiB5ZWFyYCkpLDEpKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApLA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKw0KICBmYWNldF93cmFwKHZhcnMoYXMubnVtZXJpYyh0b3BpYykpKSArDQogIGdndGl0bGUocGxvdFRpdGxlKSArDQogIHhsYWIoIlB1YmxpY2F0aW9uIFllYXIiKSArDQogIHlsYWIoIkFydGljbGUgQ291bnQiKSArDQogIGd1aWRlcyhjb2xvdXI9RkFMU0UpIA0KcHJpbnQocGFzdGUoIlBsb3Qgc2F2ZWQgYXM6IixvdXRwdXRQTkdGaWxlTmFtZSkpDQpkZXYub2ZmKCkNCg0KcGxvdFRpdGxlID0gIlJhdGlvIG9mIEFydGljbGVzIGJ5IFRvcGljIGFuZCBZZWFyIg0KIyBzYXZlIHBsb3QgaW4gcG5nIGZvcm1hdA0Kb3V0cHV0UE5HRmlsZU5hbWUgPC0gZmlsZS5wYXRoKG91dHB1dFBuZ0ZvbGRlcixwYXN0ZTAoInJhdGlvX3RvcGljX3llYXIucG5nIikpDQpwbmcob3V0cHV0UE5HRmlsZU5hbWUsaGVpZ2h0PTUsd2lkdGg9MTUsIHVuaXRzPSdpbicsIHJlcz0zMDApDQpnZ3Bsb3QoY291bnRfdG9waWNfeWVhciwgYWVzKHggPSBgUHVibGljYXRpb24geWVhcmAsIGNvbG91ciA9IGFzLmZhY3Rvcihhcy5udW1lcmljKHRvcGljKSkpKSArDQogIGdlb21fbGluZShhZXMoeSA9IHJhdGlvKSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPXNlcShtaW4obmEub21pdChjb3VudF90b3BpY195ZWFyJGBQdWJsaWNhdGlvbiB5ZWFyYCkpLCBtYXgobmEub21pdChjb3VudF90b3BpY195ZWFyJGBQdWJsaWNhdGlvbiB5ZWFyYCkpLDEpKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApLA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKw0KICBmYWNldF93cmFwKHZhcnMoYXMubnVtZXJpYyh0b3BpYykpKSArDQogIGdndGl0bGUocGxvdFRpdGxlKSArDQogIHhsYWIoIlB1YmxpY2F0aW9uIFllYXIiKSArDQogIHlsYWIoIihBcnRpY2xlIENvdW50KS8oQXJ0aWNsZSBUb3RhbCkiKSArDQogIGd1aWRlcyhjb2xvdXI9RkFMU0UpIA0KcHJpbnQocGFzdGUoIlBsb3Qgc2F2ZWQgYXM6IixvdXRwdXRQTkdGaWxlTmFtZSkpDQpkZXYub2ZmKCkNCg0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MocGFzdGUwKCJJbWFnZXMvIixyRmlsZU51bSwiX3BxX3Jldmlldy9jb3VudF90b3BpY195ZWFyLnBuZyIpKQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MocGFzdGUwKCJJbWFnZXMvIixyRmlsZU51bSwiX3BxX3Jldmlldy9jb3VudF90b3BpY195ZWFyX3RvdC5wbmciKSkNCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKHBhc3RlMCgiSW1hZ2VzLyIsckZpbGVOdW0sIl9wcV9yZXZpZXcvcmF0aW9fdG9waWNfeWVhci5wbmciKSkNCmBgYA0KDQojIENvaGVyZW5jZSBzY29yZSBieSB0b3BpYw0KYGBge3IsIG91dC53aWR0aD0iNTAlIiwgZmlnLnBvcz0iaCJ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhwYXN0ZTAoIkltYWdlcy8iLHJGaWxlTW9kZWxOdW0sIl9wcV9tb2RlbC9jb2hlcmVuY2Vfc2NvcmVfdG9waWMucG5nIikpDQpgYGANCg0KIyBXb3JkY2xvdWRzDQpgYGB7ciwgb3V0LndpZHRoPSI1MCUiLCBmaWcucG9zPSJoIn0NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKHBhc3RlMCgiSW1hZ2VzLyIsckZpbGVNb2RlbE51bSwiX3BxX21vZGVsLzFfbGRhX3RvcGljX3djLnBuZyIpKQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MocGFzdGUwKCJJbWFnZXMvIixyRmlsZU1vZGVsTnVtLCJfcHFfbW9kZWwvMl9sZGFfdG9waWNfd2MucG5nIikpDQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhwYXN0ZTAoIkltYWdlcy8iLHJGaWxlTW9kZWxOdW0sIl9wcV9tb2RlbC8zX2xkYV90b3BpY193Yy5wbmciKSkNCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKHBhc3RlMCgiSW1hZ2VzLyIsckZpbGVNb2RlbE51bSwiX3BxX21vZGVsLzRfbGRhX3RvcGljX3djLnBuZyIpKQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MocGFzdGUwKCJJbWFnZXMvIixyRmlsZU1vZGVsTnVtLCJfcHFfbW9kZWwvNV9sZGFfdG9waWNfd2MucG5nIikpDQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhwYXN0ZTAoIkltYWdlcy8iLHJGaWxlTW9kZWxOdW0sIl9wcV9tb2RlbC82X2xkYV90b3BpY193Yy5wbmciKSkNCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKHBhc3RlMCgiSW1hZ2VzLyIsckZpbGVNb2RlbE51bSwiX3BxX21vZGVsLzdfbGRhX3RvcGljX3djLnBuZyIpKQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MocGFzdGUwKCJJbWFnZXMvIixyRmlsZU1vZGVsTnVtLCJfcHFfbW9kZWwvOF9sZGFfdG9waWNfd2MucG5nIikpDQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhwYXN0ZTAoIkltYWdlcy8iLHJGaWxlTW9kZWxOdW0sIl9wcV9tb2RlbC85X2xkYV90b3BpY193Yy5wbmciKSkNCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKHBhc3RlMCgiSW1hZ2VzLyIsckZpbGVNb2RlbE51bSwiX3BxX21vZGVsLzEwX2xkYV90b3BpY193Yy5wbmciKSkNCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKHBhc3RlMCgiSW1hZ2VzLyIsckZpbGVNb2RlbE51bSwiX3BxX21vZGVsLzExX2xkYV90b3BpY193Yy5wbmciKSkNCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKHBhc3RlMCgiSW1hZ2VzLyIsckZpbGVNb2RlbE51bSwiX3BxX21vZGVsLzEyX2xkYV90b3BpY193Yy5wbmciKSkNCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKHBhc3RlMCgiSW1hZ2VzLyIsckZpbGVNb2RlbE51bSwiX3BxX21vZGVsLzEzX2xkYV90b3BpY193Yy5wbmciKSkNCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKHBhc3RlMCgiSW1hZ2VzLyIsckZpbGVNb2RlbE51bSwiX3BxX21vZGVsLzE0X2xkYV90b3BpY193Yy5wbmciKSkNCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKHBhc3RlMCgiSW1hZ2VzLyIsckZpbGVNb2RlbE51bSwiX3BxX21vZGVsLzE1X2xkYV90b3BpY193Yy5wbmciKSkNCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKHBhc3RlMCgiSW1hZ2VzLyIsckZpbGVNb2RlbE51bSwiX3BxX21vZGVsLzE2X2xkYV90b3BpY193Yy5wbmciKSkNCmBgYA0KIyBDb3VudCBvZiBBcnRpY2xlIGJ5IFRvcGljDQpgYGB7ciwgb3V0LndpZHRoPSI1MCUiLCBmaWcucG9zPSJoIn0NCiMgYmFyIGNoYXJ0DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhwYXN0ZTAoIkltYWdlcy8iLHJGaWxlTW9kZWxOdW0sIl9wcV9tb2RlbC90b3BpY19jb3VudF9iYXIucG5nIikpDQoNCiMgcGllIGNoYXJ0DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhwYXN0ZTAoIkltYWdlcy8iLHJGaWxlTW9kZWxOdW0sIl9wcV9tb2RlbC90b3BpY19jb3VudF9waWUucG5nIikpDQpgYGANCg0KIyBDb3VudCBvZiBQcm9iYWJpbGl0aWVzIGJ5IFBlcmNlbnRhZ2UNCmBgYHtyLCBvdXQud2lkdGg9IjUwJSIsIGZpZy5wb3M9ImgifQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MocGFzdGUwKCJJbWFnZXMvIixyRmlsZU1vZGVsTnVtLCJfcHFfbW9kZWwvcHJvYl9jb3VudC5wbmciKSkNCmBgYA0KIyBDb3VudCBvZiBUb3BpYyBQcm9iYWJpbGl0aWVzIGJ5IFBlcmNlbnRhZ2UNCmBgYHtyLCBvdXQud2lkdGg9IjUwJSIsIGZpZy5wb3M9ImgifQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MocGFzdGUwKCJJbWFnZXMvIixyRmlsZU1vZGVsTnVtLCJfcHFfbW9kZWwvcHJvYl90b3BpY19jb3VudC5wbmciKSkNCmBgYA0KIzUuIFZpc3VhbGlzaW5nIG9mIHRvcGljcyBpbiBhIGRlbmRyb2dyYW0NCmBgYHtyLCBvdXQud2lkdGg9IjUwJSIsIGZpZy5wb3M9ImgifQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MocGFzdGUwKCJJbWFnZXMvIixyRmlsZU1vZGVsTnVtLCJfcHFfbW9kZWwvaGNsdXN0X2RlbmRyb2dyYW0ucG5nIikpDQoNCmBgYA0KDQojICJRdWFyYW50aW5lIg0KYGBge3J9DQojIG5vdF9veXN0ZXJfc3Vic2V0ID0gaWYgbm90IGV4aXN0cyBpbiBmdWxsX2NvcnB1cyB7b3lzdGVyKn0NCiMgMTg1OCBhcnRpY2xlcyB3aXRoIHRoZSB3b3JkICJveXN0ZXIiDQojIDMyODYgYXJ0aWNsZXMgd2l0aG91dCB0aGUgd29yZCAib3lzdGVyIg0KcHFfbWV0YXF0aW5lPC0gcHFfbWV0YWpvaW4gJT4lIA0KICBmaWx0ZXIoIXN0cmluZ3I6OnN0cl9kZXRlY3QoYEZ1bGwgdGV4dGAscmVnZXgoIm95c3RlciIsIGlnbm9yZV9jYXNlID0gVCkpKSANCnByaW50KHBhc3RlKCJOdW1iZXIgb2YgYXJ0aWNsZXMgd2l0aG91dCB0aGUgd29yZCBveXN0ZXIqIiwgbnJvdyhwcV9tZXRhcXRpbmUpKSkNCg0KaGVhZChwcV9tZXRhcXRpbmUpDQoNCiMgcXVhcmFudGluZWRfc3Vic2V0ID0gaWYgZXhpc3RzIGluIG5vdF9veXN0ZXJfc3Vic2V0IHtjbGFtKiB8IG11c3NlbCogfCBzY2FsbG9wKiB8IGxvYnN0ZXIqfQ0KIyBtYXliZSBhZGQgY3JhYj8NCiNUaGVzZSBhcmUgdGhlIHF1YXJhbnRpbmVkIGFydGljbGVzIHRoYXQgbmVlZCB0byBiZSBleGFtaW5lZA0KcHFfbWV0YXF0aW5lIDwtIHBxX21ldGFxdGluZSAlPiUgDQogIGZpbHRlcihzdHJpbmdyOjpzdHJfZGV0ZWN0KGBGdWxsIHRleHRgLHJlZ2V4KCJjbGFtfG11c3NlbHxzY2FsbG9wfGxvYnN0ZXJ8Y3JhYiIsIGlnbm9yZV9jYXNlID0gVCkpKQ0KcHJpbnQocGFzdGUoIk51bWJlciBvZiBhcnRpY2xlcyB3aXRoaW4gKm5vdCBveXN0ZXIqIHN1YnNldCBhbmQgaGFzIHRoZSB3b3JkcyBjbGFtfG11c3NlbHxzY2FsbG9wfGxvYnN0ZXJ8Y3JhYiIsIG5yb3cocHFfbWV0YXF0aW5lKSkpDQoNCnBxX21ldGFxdGluZV9pZHMgPC0gcHFfbWV0YXF0aW5lICU+JSBzZWxlY3QoYFByb1F1ZXN0IGRvY3VtZW50IElEYCkNCiMgY29ycHVzX3N1YnNldCA9IGFudGlfam9pbihxdWFyYW50aW5lZF9zdWJzZXQsIGZ1bGxjb3JwdXMpDQoNCiMgdGhpcyBpcyB0aGUgZW50aXJlIGNvcnB1cyB3aXRoIHF1YXJhbnRpbmVkIGFydGljbGVzIHJlbW92ZWQNCnBxX21ldGFmaWx0ZXJfd29yZHMgPC0gcHFfbWV0YWpvaW4gJT4lDQogIGFudGlfam9pbihwcV9tZXRhcXRpbmVfaWRzLCBieT0iUHJvUXVlc3QgZG9jdW1lbnQgSUQiKQ0KDQpudW1GaWx0ZXJlZCA8LSBucm93KHBxX21ldGFqb2luKS1ucm93KHBxX21ldGFmaWx0ZXJfd29yZHMpDQpybShwcV9tZXRhcXRpbmVfaWRzKQ0KDQpwcmludChwYXN0ZSgiTnVtYmVyIG9mIGFydGljbGVzIHJlbW92ZWQgYmFzZWQgb24gd29yZHM6IixudW1GaWx0ZXJlZCkpO25yb3cocHFfbWV0YWZpbHRlcl93b3JkcykNCg0KYGBgDQojIFNUQVJUIFNVQlNFVFRJTkcgLSBTdWJzZXQgRnVuY3Rpb24NCiMjIyMgdG9waWNfc3Vic2V0X2NzdiA8LSBmdW5jdGlvbihvdXRwdXRGb2xkZXJOYW1lLCByRmlsZU51bSwgb3V0cHV0RmlsZU5hbWUsIGlucHV0Q29ycHVzLCBtaW5QZXJjLCBtYXhQZXJjLCB0aGVUb3BpYywgb3ZlcndyaXRlKQ0KDQoqIG91dHB1dEZvbGRlck5hbWUgPSBmb2xkZXIgdG8gc2F2ZSB0byAtLSBpLmUuLCAiRGF0YS8wMl9Xb3JraW5nLyIgKHRoaXMgaXMgc2V0IGF0IHRoZSB0b3Agb2YgdGhlIGNvZGUgd2hlcmUgdGhlIGxpYnJhcmllcyBhcmUgbG9hZGVkL2luc3RhbGxlZCkNCiogckZpbGVOdW0gPSBUaGUgbnVtYmVyIGluIHRoZSByIGZpbGUgLS0gaS5lLiwgIjA1IiAodGhpcyBpcyBzZXQgYXQgdGhlIHRvcCBvZiB0aGUgY29kZSB3aGVyZSB0aGUgbGlicmFyaWVzIGFyZSBsb2FkZWQvaW5zdGFsbGVkKQ0KKiBvdXRwdXRGaWxlTmFtZSA9IHRoZSBmaWxlIG5hbWUgdG8gc2F2ZSBhcyAtLSBpLmUuLCAicHFfdG9waWM1XzkwcGVyYyIgKHRoaXMgbmVlZHMgdG8gYmUgc2V0IGF0IGxlYXN0IDEgbGluZSBhYm92ZSB3aGVyZSB0aGUgZnVuY3Rpb24gaXMgcmFuKQ0KKiBpbnB1dENvcnB1cyA9IHRoZSBsYWJlbGVkIGNvcnB1cyAtLSBpLmUuLCBwcV9tZXRham9pbiAodGhpcyBpcyBjcmVhdGVkIHNob3J0bHkgYWZ0ZXIgd2hlcmUgbGlicmFyaWVzIGFyZSBsb2FkZWQvaW5zdGFsbGVkIGluIHJldmlldy5SbWQgYnkgam9pbmluZyBwcV9tZXRhY2xlYW4gd2l0aCBwcV9sYWJlbHMpDQoqIG1pblBlcmMgPSBtaW5pbXVtIHBlcmNlbnRhZ2UgLS0gaS5lLiwgMC45ICh0aGlzIG5lZWRzIHRvIGJlIHNldCBhdCBsZWFzdCAxIGxpbmUgYWJvdmUgd2hlcmUgdGhlIGZ1bmN0aW9uIGlzIHJhbikNCiogbWF4UGVyYyA9IG1heGltdW0gcGVyY2VudGFnZSAtLSBpLmUuLCAxICh0aGlzIG5lZWRzIHRvIGJlIHNldCBhdCBsZWFzdCAxIGxpbmUgYWJvdmUgd2hlcmUgdGhlIGZ1bmN0aW9uIGlzIHJhbikNCiogdGhlVG9waWMgPSB0aGUgdG9waWMgY2F0ZWdvcnkgLS0gaS5lLiwgIjEiICh0aGlzIG5lZWRzIHRvIGJlIHNldCBhdCBsZWFzdCAxIGxpbmUgYWJvdmUgd2hlcmUgdGhlIGZ1bmN0aW9uIGlzIHJhbikNCiogb3ZlcndyaXRlID0gd2hldGhlciB0byBvdmVyd3JpdGUgdGhlIGZpbGUgaWYgaXQgYWxyZWFkeSBleGlzdHMgLS0gaS5lLiwgRkFMU0UgKHRoaXMgbmVlZHMgdG8gYmUgc2V0IGF0IGxlYXN0IDEgbGluZSBhYm92ZSB3aGVyZSB0aGUgZnVuY3Rpb24gaXMgcmFuKQ0KDQojIFN1YnNldCB0b3BpYyAxDQpgYGB7cn0NCiMgc3Vic2V0IGNvcnB1cyB0byB1bmlxdWUgaWRlbnRpZmllciAmIGZ1bGwgdGV4dCBvZiBhcnRpY2xlDQojIGludmVzdGlnYXRlIHRvcGljIDENCg0Kb3V0cHV0RmlsZU5hbWVfMXQgPC0gInBxX3RvcGljMSINCm1pblBlcmMgPC0gMA0KbWF4UGVyYyA8LSAxDQp0aGVUb3BpYyA8LSAiMSINCg0KIyMgZS5nLCBtaW5QZXJjID0gMCAmIG1heFBlcmMgPSAwLjQgaXMgemVybyB0byAwLjM5OTk5OTk5OTkgLi4uDQpwcV90b3BpYzFfc3Vic2V0PC10b3BpY19zdWJzZXRfY3N2KG91dHB1dEZvbGRlciwgckZpbGVOdW0sIG91dHB1dEZpbGVOYW1lXzF0LCBwcV9tZXRham9pbiAsIG1pblBlcmMsIG1heFBlcmMsIHRoZVRvcGljLCBvdmVyd3JpdGUpDQoNCmhlYWQocHFfdG9waWMxX3N1YnNldCk7bnJvdyhwcV90b3BpYzFfc3Vic2V0KQ0KYGBgDQoNCiMgU3Vic2V0IHRvcGljIDEsIDkwJQ0KYGBge3J9DQojIHN1YnNldCBjb3JwdXMgdG8gdW5pcXVlIGlkZW50aWZpZXIgJiBmdWxsIHRleHQgb2YgYXJ0aWNsZQ0KIyBpbnZlc3RpZ2F0ZSB0b3BpYyAxLCA5MCUNCm91dHB1dEZpbGVfMXQ5MHBlcmMgPC0gInBxX3RvcGljMV85MHBlcmMiDQptaW5QZXJjIDwtIDAuOQ0KbWF4UGVyYyA8LSAxDQp0aGVUb3BpYyA8LSAiMSINCg0KIyMgZS5nLCBtaW5QZXJjID0gMCAmIG1heFBlcmMgPSAwLjQgaXMgemVybyB0byAwLjM5OTk5OTk5OTkgLi4uDQpwcV90b3BpYzFfOTBwZXJjPC10b3BpY19zdWJzZXRfY3N2KG91dHB1dEZvbGRlciwgckZpbGVOdW0sIG91dHB1dEZpbGVfMXQ5MHBlcmMsIHBxX21ldGFqb2luICwgbWluUGVyYywgbWF4UGVyYywgdGhlVG9waWMsIG92ZXJ3cml0ZSkNCg0KaGVhZChwcV90b3BpYzFfOTBwZXJjKTtucm93KHBxX3RvcGljMV85MHBlcmMpDQpgYGANCg0KDQojIFN1YnNldCB0b3BpYyA1LCA5MCUNCmBgYHtyfQ0KIyBzdWJzZXQgY29ycHVzIHRvIHVuaXF1ZSBpZGVudGlmaWVyICYgZnVsbCB0ZXh0IG9mIGFydGljbGUNCiMgaW52ZXN0aWdhdGUgdG9waWMgMSwgOTAlDQpvdXRwdXRGaWxlXzV0OTBwZXJjIDwtICJwcV90b3BpYzVfOTBwZXJjIg0KbWluUGVyYyA8LSAwLjkNCm1heFBlcmMgPC0gMQ0KdGhlVG9waWMgPC0gIjUiDQoNCiMjIGUuZywgbWluUGVyYyA9IDAgJiBtYXhQZXJjID0gMC40IGlzIHplcm8gdG8gMC4zOTk5OTk5OTk5IC4uLg0KcHFfdG9waWM1XzkwcGVyYzwtdG9waWNfc3Vic2V0X2NzdihvdXRwdXRGb2xkZXIsIHJGaWxlTnVtLCBvdXRwdXRGaWxlXzV0OTBwZXJjLCBwcV9tZXRham9pbiAsIG1pblBlcmMsIG1heFBlcmMsIHRoZVRvcGljLCBvdmVyd3JpdGUpDQoNCmhlYWQocHFfdG9waWM1XzkwcGVyYyk7bnJvdyhwcV90b3BpYzVfOTBwZXJjKQ0KYGBgDQoNCg0KIyBTdWJzZXQgNTAtNjAlLWVycw0KYGBge3J9DQojIHN1YnNldCBjb3JwdXMgdG8gdW5pcXVlIGlkZW50aWZpZXIgJiBmdWxsIHRleHQgb2YgYXJ0aWNsZQ0KIyBpbnZlc3RpZ2F0ZSBhbGwgdG9waWNzLCA1MC02MCUNCm91dHB1dEZpbGVfNTZwZXJjIDwtICJwcV81NnBlcmMiDQptaW5QZXJjIDwtIDAuNQ0KbWF4UGVyYyA8LSAwLjYNCnRoZVRvcGljIDwtICJhbGwiDQoNCiMjIGUuZywgbWluUGVyYyA9IDAgJiBtYXhQZXJjID0gMC40IGlzIHplcm8gdG8gMC4zOTk5OTk5OTk5IC4uLg0KcHFfNTZwZXJjPC10b3BpY19zdWJzZXRfY3N2KG91dHB1dEZvbGRlciwgckZpbGVOdW0sIG91dHB1dEZpbGVfNTZwZXJjLCBwcV9tZXRham9pbiAsIG1pblBlcmMsIG1heFBlcmMsIHRoZVRvcGljLCBvdmVyd3JpdGUpDQoNCmhlYWQocHFfNTZwZXJjKTtucm93KHBxXzU2cGVyYykNCmBgYA0KDQojIFN1YnNldCB0b3BpYyAxDQpgYGB7cn0NCiMgc3Vic2V0IGNvcnB1cyB0byB1bmlxdWUgaWRlbnRpZmllciAmIGZ1bGwgdGV4dCBvZiBhcnRpY2xlDQojIGludmVzdGlnYXRlIHRvcGljIDENCiMgaWYgd2l0aGluIHRoZSBmdW5jdGlvbiB5b3Ugd3JpdGUgb3ZlcndyaXRlPVRSVUUsIHRoZW4gdGhlIG91dHB1dCBDU1YgZmlsZSB3aWxsIGJlIHJlLXdyaXR0ZW4uIA0KIyBvdmVyd3JpdGUsIG91dHB1dEZvbGRlciwgckZpbGVOYW1lIGFyZSBzZXQgaW4gdGhlIGZpcnN0IGNodW5rIG9mIGNvZGUNCg0KDQpvdXRwdXRGaWxlTmFtZV8wNnBlcmMgPC0gInBxXzA2cGVyYyINCm1pblBlcmMgPC0gMA0KbWF4UGVyYyA8LSAwLjYNCnRoZVRvcGljIDwtICJhbGwiDQoNCiMjIGUuZywgbWluUGVyYyA9IDAgJiBtYXhQZXJjID0gMC42IGlzIHplcm8gdG8gMC41OTk5OTk5OSAuLi4NCnBxX3BlcmMwNl9zdWJzZXQ8LXRvcGljX3N1YnNldF9jc3Yob3V0cHV0Rm9sZGVyLCByRmlsZU51bSwgb3V0cHV0RmlsZU5hbWVfMDZwZXJjLCBwcV9tZXRham9pbiAsIG1pblBlcmMsIG1heFBlcmMsIHRoZVRvcGljLCBvdmVyd3JpdGUpDQoNCmhlYWQocHFfcGVyYzA2X3N1YnNldCk7bnJvdyhwcV90b3BpYzFfc3Vic2V0KQ0KdW5pcXVlKHBxX3BlcmMwNl9zdWJzZXQkdG9waWMpDQptaW4ocHFfcGVyYzA2X3N1YnNldCR2YWwpDQptYXgocHFfcGVyYzA2X3N1YnNldCR2YWwpDQpgYGANCg0KIyBFTkQgU1VCU0VUVElORw0KDQojIGV4cGxvcmUgdGVybSBmcmVxdWVuY2llcyBieSB5ZWFyIC0gaG93IGRvIHdvcmRzIGluIG91ciBjb3JwdXMgY2hhbmdlIG92ZXIgdGltZT8NClJlZmVyZW5jZWQgd2Fsay10aHJvdWdoIGZyb206IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy90aWR5dGV4dC92aWduZXR0ZXMvdGlkeWluZ19jYXN0aW5nLmh0bWwNCmBgYHtyfQ0KIyBzdWJzZXQgY29ycHVzIHRvIHVuaXF1ZSBpZGVudGlmaWVyICYgeWVhcg0KcHFfdGltZSA8LSBwcV9tZXRhY2xlYW4gJT4lIA0KICBzZWxlY3QoYFByb1F1ZXN0IGRvY3VtZW50IElEYCwgYFB1YmxpY2F0aW9uIHllYXJgKQ0KDQojIHRva2VucyBnZW5lcmF0ZWQgZnJvbSAwNF9wcV9tb2RlbC5SbWQNCm91dHB1dFRva2VuRmlsZSA9IHBhc3RlMChyRmlsZU1vZGVsTnVtLCJfdG9rZW5zLlJEYXRhIikNCnRva2Vuc0ZpbGVOYW1lID0gZmlsZS5wYXRoKG91dHB1dEZvbGRlcixvdXRwdXRUb2tlbkZpbGUpDQoNCmxvYWQoZmlsZT10b2tlbnNGaWxlTmFtZSkNCg0KdG9rZW5zIDwtIHRva2VucyAlPiUgDQogIGZ1bGxfam9pbihwcV90aW1lLCBieSA9IGMoIlByb1F1ZXN0IGRvY3VtZW50IElEIiA9ICJQcm9RdWVzdCBkb2N1bWVudCBJRCIpKSAlPiUNCiAgcmVuYW1lKFllYXIgPSBgUHVibGljYXRpb24geWVhcmApICU+JQ0KICByZW5hbWUocHFfaWQgPSBgUHJvUXVlc3QgZG9jdW1lbnQgSURgKSAlPiUNCiAgbXV0YXRlX2F0KHZhcnMoWWVhciksIGZ1bnMoYXMuaW50ZWdlcikpDQpybShwcV90aW1lKQ0KDQpvdXRwdXRGcmVxc0ZpbGUgPSBwYXN0ZTAockZpbGVOdW0sIl90b2tlbnNfZnJlcSIpDQp0b2tlbnNfZnJlcTwtY3JlYXRlX2lmbm90X3Rva2Vuc19mcmVxKG91dHB1dEZvbGRlciwgb3V0cHV0RnJlcXNGaWxlLCB0b2tlbnMsIG92ZXJ3cml0ZSkNCg0Kb3V0cHV0RnJlcU1vZGVsRmlsZSA9IHBhc3RlMChyRmlsZU51bSwiX2ZyZXFfbW9kZWxzIikNCmZyZXFfbW9kZWxzIDwtIGNyZWF0ZV9pZm5vdF9mcmVxbW9kZWxzKG91dHB1dEZvbGRlciwgb3V0cHV0RnJlcU1vZGVsRmlsZSwgdG9rZW5zX2ZyZXEsIG92ZXJ3cml0ZSkNCmBgYA0KDQojIG1vZGVsIHJlc3VsdHMNCmBgYHtyfQ0KZnJlcV9tb2RlbHMgJT4lDQogIGZpbHRlcih0ZXJtID09ICJZZWFyIikgJT4lDQogIGFycmFuZ2UoZGVzYyhhYnMoZXN0aW1hdGUpKSkNCmBgYA0KDQojIE1vZGVscyBkaXNwbGF5ZWQgYXMgYSB2b2xjYW5vIHBsb3QsIHdoaWNoIGNvbXBhcmVzIHRoZSBlZmZlY3Qgc2l6ZSB3aXRoIHRoZSBzaWduaWZpY2FuY2UNCmBgYHtyfQ0KZnJlcV9tb2RlbHMgJT4lDQogIG11dGF0ZShhZGp1c3RlZC5wLnZhbHVlID0gcC5hZGp1c3QocC52YWx1ZSkpICU+JQ0KICBnZ3Bsb3QoYWVzKGVzdGltYXRlLCBhZGp1c3RlZC5wLnZhbHVlKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBzY2FsZV95X2xvZzEwKCkgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gd29yZCksIHZqdXN0ID0gMSwgaGp1c3QgPSAxLA0KICAgICAgICAgICAgY2hlY2tfb3ZlcmxhcCA9IFRSVUUpICsNCiAgeGxhYigiRXN0aW1hdGVkIGNoYW5nZSBvdmVyIHRpbWUiKSArDQogIHlsYWIoIkFkanVzdGVkIHAtdmFsdWUiKQ0KYGBgDQoNCiMgVG9wIDYgdGVybXMgdGhhdCBoYXZlIGNoYW5nZWQgaW4gZnJlcXVlbmN5IG92ZXIgdGltZQ0KYGBge3J9DQpmcmVxX21vZGVscyAlPiUNCiAgdG9wX24oNiwgYWJzKGVzdGltYXRlKSkgJT4lDQogIGlubmVyX2pvaW4odG9rZW5zX2ZyZXEpICU+JQ0KICBnZ3Bsb3QoYWVzKFllYXIsIHBlcmNlbnQpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKCkgKw0KICBmYWNldF93cmFwKH4gd29yZCkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gcGVyY2VudF9mb3JtYXQoKSkgKw0KICB5bGFiKCJGcmVxdWVuY3kgb2Ygd29yZCBpbiBzcGVlY2giKQ0KYGBgDQo=